JNIWrapper Tutorial Version: 3.12 Last Updated: October 21, 2016
Copyright © 2002-2021 TeamDev Ltd. Introduction
Welcome to JNIWrapperTM for Microsoft Windows
tutorial. It is designed to illustrate the creation of a simple
application with certain features not provided by the Java platform and to
demonstrate the basic concepts you need to know to develop successful
applications using JNIWrapper. The program we are going to create will reveal JNIWrapper's
capabilities through simple Win32 API functions. The first thing we'll do
is to make some sounds using the system speaker. It's not very often that
you hear Java programs are capable of such functionality, so this is the
simplest way to make our application stand out. We'll call the program
"Buzzer" to reflect the sound effect it produces and also add other nice
features while going through the tutorial. The code will evolve as new concepts are introduced. By following
the tutorial step by step and adding code, you will finally have a
complete working program. The full Buzzer sample code is available for download
from the JNIWrapper site. Chapter 1. Set-up
1.1. Creating the EnvironmentFirst things first. Let's set up the program working environment.
Create a directory for the program and call it Sample. We will store our
Java file, JNIWrapper library and any other resources here. It will also
be our working directory for the sample application. In the working
directory, let's create a directory for native files and call it
bin. Now copy the Java library
jniwrap-3.1.jar and
winpack-3.0.jar to the Sample directory, the native
DLL (jniwrap.dll) and license files
(jniwrap.lic) to the
Sample\bin directory. Create a Java application
file in the Sample directory and call it
Buzzer. You should now have the following structure: Sample\
bin\
jniwrap.dll
jniwrap.lic
Buzzer.java
jniwrap-3.1.jar
winpack-3.0.jar 1.2. Creating the ApplicationNow let's set up the class of the application and run it before
moving to the actual JNIWrapper coding. Put the following code into
Buzzer.java: import com.jniwrapper.*;
public class Buzzer
{
public Buzzer()
{
}
public static void main(String[] args)
{
System.out.println("The Buzzer is running");
Buzzer buzzer = new Buzzer();
}
} Compile the class: javac -classpath jniwrap-3.1.jar;winpack-3.0.jar Buzzer.java and run the application: java -classpath jniwrap-3.1.jar;winpack-3.0;. Buzzer In this tutorial, compiling and running of the application should
be understood as two actions being performed. Remember that you need to
run the commands from the Sample directory for the
program to work. OK, let's get to real work. Chapter 2. Working with Native Libraries
As was mentioned earlier, the first thing we'll try in our program
is to make sounds using the system speaker. To do this, we'll use the
standard Win32 Beep function. This function is
exported from kernel32.dll, so our application
requires this to be loaded. JNIWrapper provides a shortcut method for
calling a function that loads a library and invokes its method. But the
library itself provides a lot of interesting functions, so we'd better
load it once to get access to the full variety of functions that we may
choose to invoke. 2.1. Preparing a Search PathBefore doing any library loading, we need to prepare a search path
for our libraries, namely JNIWrapper DLL and those we'll use for
invoking functions. By default, libraries are located by the
DefaultLibraryLoader singleton instance. This
class searches for libraries following the path specified by the
java.library.path system property; though, using
its addPath method, we can add new directories
there. All standard Win32 libraries that we will use are on the system
PATH, which is automatically added to
java.library.path. So we just need to add a search
path for the JNIWrapper DLL. (We could copy it to some location on the
system PATH, but it is generally considered not a good style, especially
for deployed applications.) The DLL is in the bin
folder and since we run only from the current directory, let's use the
relative path to it. We'll use the relative paths later to keep the
example simpler. So, in our main function, we'll add a line as shown
below: DefaultLibraryLoader.getInstance().addPath("bin"); You'll need to configure the library loader in all programs that
use JNIWrapper before any native-related activity takes place. 2.2. Loading Native Code LibrariesLet's get back to our library. To load a library, you should
create an object of the com.jniwrapper.Library type
specifying the library name. Since kernel32.dll
will be used often in our program, we'll create a field for it and load
it to the constructor. Now we need to change our class in the following
way (the new code is given in bold): private Library _kernel;
public Buzzer()
{
_kernel = new Library("kernel32");
} There is no need to specify the extension: it is appended
automatically. Libraries are loaded when their objects are constructed.
You don't have to worry about freeing libraries: if a library object is
no longer used, the native library defined by it is unloaded. Now let's
compile and run the application to make sure everything is typed
correctly and all the libraries are available. Chapter 3. Using Simple Types
In this chapter, we'll make beeping sounds by invoking a native
function with simple parameters. Let's put our "beeping" code in a new
method called buzz. As was mentioned earlier, we'll use the
Beep function to produce sounds. This function
takes two DWORD parameters and returns a BOOL
value. We are not really interested in the return value and will ignore
it, since this function is unlikely to fail. However, we need to pass
values, so we'll create objects for the function parameters: private void buzz()
{
// Prepare beep parameters: low and high frequencies and beep durations
UInt32 low = new UInt32(400);
UInt32 high = new UInt32(1000);
UInt32 durLow = new UInt32(200);
UInt32 durHigh = new UInt32(200); We used the standard JNIWrapper's UInt32 type which is
exactly what DWORD is: a 32-bit unsigned integer. Our method
will produce a series of lower and higher frequency beeps, that's why we
define two sets of parameters: [low; durLow] and
[high; durHigh]. In JNIWrapper, all numeric types, like Int,
SingleFloat, and so on, have constructors that specify the
initial value and also have getters and setters for the value. All
parameters in JNIWrapper are mutable: they behave like variables. We could
use one pair of parameters and modify their values between calls, but it's
less illustrative, so let's have two sets. Chapter 4. Invoking Functions
Now that we have the library and parameters, one thing is still
missing: the function to invoke. Functions are obtained from the library
that exports them. To get a function, you should use the name it is
exported with. Luckily, most Win32 API functions are exported with the
same names (or almost the same - we will get to this case later) with
which they appear in the documentation. To get the
Beep function, let's use the following
code: // Obtain function reference from the library
Function beep = _kernel.getFunction("Beep"); Everything is ready to make beeping sounds. We'll create a small
loop in which the Beep function is invoked to
make high and low frequency beeps five times. // Do the beeping
for (int i = 0; i < 5; i++)
{
beep.invoke(null, high, durHigh);
beep.invoke(null, low, durLow);
} Note that the first argument of the invoke
method is null. This is to indicate that we do not
care about the return value. Later, we can easily add checking for it.
Imagine the consequences of incorrectly ignoring the return value in the
conventional JNI implementation: to get it later, you would need to modify
the native method signature, change the native implementation and Java
usages, and rebuild both Java and native code. This is just a simple
example of how JNIWrapper can significantly save you development
time. Our new method is complete. Let's invoke it from the main method to
see how it works: public static void main(String[] args)
{
// Add JNIWrap.dll directory to library loader search path
DefaultLibraryLoader.getInstance().addPath("bin");
Buzzer buzzer = new Buzzer();
buzzer.buzz();
} Compile and run the application. As a result, you should hear an
alarm-like sound from the speaker. Chapter 5. Using Strings
Our next goal is to allow the user to run only one instance of the
application at a time. Many Java programs try to achieve this by binding a
particular TCP/IP socket. This approach does not ensure success because
you cannot be absolutely sure the selected socket number is good. Our program will use a different method of creating a named mutex on
start and checking for its existence to find out if another instance is
running. With a well-chosen name, this method is practically bullet-proof.
To create a mutex, we need to specify its name, which is a string. 5.1. String TypesIn Java, we have only one type of strings: the Unicode ones.
Native side, however, has both single-byte (char*) and wide
(wchar_t*) strings. JNIWrapper supports both types
providing two concrete parameter classes:
AnsiString for single-byte strings and
WideString for wide strings. All strings we
create in this application will consist only of 7-bit pure ASCII
characters, so we'll use AnsiStrings. To make sure that our single-instance mechanism is working
properly, we need to make our program keep running for as long as
needed. One of the simplest ways to achieve this is to display a message
box at the stage where we would like to wait. Of course, we could use
JOptionPane here, but it's not in the spirit of this tutorial and
besides, it does not produce that cool native notification sound. Let's
prepare a method that would allow displaying information or error
message box: private void showMessageBox(String message, int flags) The first argument here is the message itself, whereas the second
one is the flags mask for the MessageBox
function we are going to invoke. Let's take a closer look at the
MessageBox function. From the documentation, we
can see that it has the following signature: int MessageBox(
HWND hWnd, // handle to the owner window
LPCTSTR lpText, // text in the message box
LPCTSTR lpCaption, // message box title
UINT uType // message box style
); 5.2. Creating Mutex and Displaying a MessageWe need to create a correct parameter set to invoke this function.
The simplest way would be the UINT type as it has an
equivalent type in JNIWrapper, i.e. UInt. As far as
HWND is concerned, it is a handle and, therefore, a
pointer. Generally speaking, pointers are more complex than the things
we would like to touch upon in this chapter. Luckily, the data this
pointer is pointing to will never be used, so we don't need to fill in
this pointer's referenced area with any data. And last, but not least.
In our case this pointer is simply NULL. On our
platform, we could use a UInt32 pararameter since we know
that all pointers are unsigned 32-bit integers, but a more correct way
would be to use a Pointer.Void parameter. We'll set its
handle value to zero. This corresponds to a C-like conversion from an
integer. Whenever you need a constant such as
(HWND)-1, use Pointer.Void specifying
the value in the constructor or setter. The rest of the parameters are strings. The LPCTSTR
type defines a pointer to a string that is ANSI or Unicode, depending on
the build configuration. Behind the scenes, it means that there are two
versions for this function for each of the string formats. In Win32 API
their names are created from a function name by appending 'A' for the
ANSI version or 'W' for Unicode. We would like to use ANSI, so our target function is spelled
MessageBoxA. The arguments are string pointers,
so in order to pass them, we create instances of
AnsiString. Strings in C/C++ are pointers to
string data. In JNIWrapper, string parameters actually represent string
data which, to become string arguments, need to be pointed to. But when
we are calling a function, its string parameters are automagically
passed to the underlying function as a pointer to relieve programmers
from the burden of creating any extra pointers. Therefore, the string
parameters for the MessageBoxA function are
defined just like AnsiString. Here's the argument preparation code for our method: Pointer.Void hWnd = new Pointer.Void(0);
AnsiString text = new AnsiString(message);
AnsiString caption = new AnsiString("Buzzer");
UInt uFlags = new UInt(flags); To invoke the MessageBoxA function, we
need another library - user32.dll. Add the
following code in bold to the declarations and constructor: private Library _kernel;
private Library _user;
public Buzzer()
{
_kernel = new Library("kernel32");
_user = new Library("user32");
} Let's finish the showMessage
method: Function messageBox = _user.getFunction("MessageBoxA");
messageBox.invoke(null, hWnd, text, caption, uFlags); Here, we also can safely ignore the return value. For further convenience, we'll create methods to display the error
and information message boxes. The methods will specify the required
style constants for those message box types. private void message(String message)
{
showMessageBox(message, 0x30);
}
private void error(String message)
{
showMessageBox(message, 0x10);
} 5.3. Passing Mutex Name ParameterWith what we have already learned, it is really easy to write a
mutex-based single instance checking part. First, let's create a parameter representing a mutex name: private boolean checkOneInstance()
{
AnsiString mutexName = new AnsiString("com.jniwrapper.sample.BuzzerMutex"); Then, let's try to open a mutex with this name: UInt32 desiredAcces = new UInt32(0x1F0001);
Bool inheritHandle = new Bool(false);
Pointer.Void mutexHandle = new Pointer.Void();
Function openMutex = _kernel.getFunction("OpenMutexA");
openMutex.invoke(mutexHandle, desiredAcces, inheritHandle, mutexName); Here, we are interested in the result. If there is no such mutex,
the function returns NULL, otherwise we'll know
that another instance is running and we need to close the new instance.
To get the return value, we need to create a variable of the required
type and pass it as the first argument to the
invoke method. After the invocation is
complete, the passed parameter contains the return value. Next, we need
to test the return value: if (!mutexHandle.isNull())
{
// Mutex exists - one instance is already running
return false;
} 5.4. Creating Locking MutexNow if the mutex doesn't exist, this instance is the first one and
therefore we need to create a locking mutex. Creating is not much
different from checking, except for the part where the
NULL result now indicates the error we would like
to catch. Here is the rest of the checking method: // Not yet running - lock by creating mutex
Pointer.Void mutexAttributes = new Pointer.Void(0);
Bool initialOwner = new Bool(false);
Function createMutex = _kernel.getFunction("CreateMutexA");
createMutex.invoke(mutexHandle, mutexAttributes, initialOwner, mutexName);
if (mutexHandle.isNull())
{
throw new RuntimeException("Mutex creation failed,
last error = " + getLastError(true));
}
return true;
} 5.5. Error HandlingYou have probably noticed that the last piece of code references a
method that is not yet defined: getLastError.
It needs to return the last system error code, optionally clearing the
error status depending on a boolean parameter. Let's implement it now.
We already know all methods and types to write the following simple
piece of code: private long getLastError(boolean clear)
{
UInt32 r = new UInt32();
Function getLE = _kernel.getFunction("GetLastError");
getLE.invoke(r);
long errCode = r.getValue();
if (clear)
{
UInt32 zero = new UInt32(0);
Function setLE = _kernel.getFunction("SetLastError");
setLE.invoke(null, zero);
}
return errCode;
} 5.6. Finishing IterationWe are ready to finish this iteration by adding the following code
in bold to the main method: public static void main(String[] args)
{
// Add JNIWrap.dll directory to library loader search path
DefaultLibraryLoader.getInstance().addPath("bin");
Buzzer buzzer = new Buzzer();
if (!buzzer.checkOneInstance())
{
buzzer.error("Buzzer is already running");
System.exit(0);
}
buzzer.message("Buzzer started, click OK to close");
} Compile and run the application. As a result, you should see the
"Buzzer started..." message box - don't close it and try to run another
instance. Now you should see an error message box saying that the
application is already running. Chapter 6. Using Callbacks
In this chapter, let's create a timer to make a sound after some
time elapses. We'll use Windows native timers here, so we need a way to
have the native code to call Java code. JNIWrapper provides such a
capability using the com.jniwrapper.Callback class.
You can create any number of callbacks that can have any parameters and
return values. Creating a callback is simple: you need to subclass the
Callback class, define the callback arguments and
return value, and implement the callback method.
Then, you pass an instance of this class as a parameter wherever you need
a reference to the callback. 6.1. Creating a Timer CallbackWe are going to use the Windows API
SetTimer function. It accepts the reference to
the TimerProc callback function, so we need to
implement such a callback. Here's its definition: VOID CALLBACK TimerProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_TIMER message
UINT_PTR idEvent, // timer identifier
DWORD dwTime // current system time
); After examining the function signature, we can see we already know
how to pass any of such types to a function call. Specifying them as
callback arguments is no harder. Let's define our callback class as an
inner class of our application class so that we can easily call the
useful methods defined there: private class TimeOutCallback extends Callback
{
private Pointer.Void _hwnd = new Pointer.Void();
private UInt _msg = new UInt();
private UInt _timerID = new UInt();
private UInt32 _time = new UInt32();
public TimeOutCallback()
{ We have defined the fields for each of our callback parameters.
There is no return value, so we do not define any parameter for it. In
the constructor, we need to configure our callback by specifying
parameters of the callback signature: init(new Parameter[] {
_hwnd,
_msg,
_timerID,
_time
}, null);
} The last null is the void return
value. Now, to make this class complete and compilable, we'll implement
the callback method. We'll make it just a buzz
but will add more intelligent code to it later in this chapter: public void callback()
{
buzz();
}
} The callback created is already usable. Easy, isn't it? Just
imagine doing all this stuff using the conventional JNI
techniques! 6.2. Using a Callback in the ApplicationNow we need to create a timer and pass the callback reference to
be called when the timer elapses. To do this, we'll create a method and
call it startTimer. This function requires passing a window handle. We
will use the Wnd class that can obtain a native
window handle from any Swing component. In subsequent iterations, we'll
get our own window handle without messing up with internal
implementation classes. For now, let's create a Java window and use its
handle. Add the following code in bold to the class
initialization: import com.jniwrapper.*;
import com.jniwrapper.win32.ui.Wnd;
import java.awt.*;
import sun.awt.windows.WToolkit;
import javax.swing.*;
public class Buzzer
{
private Library _kernel;
private Library _user;
private Window _window;
private static final int TIMER_ID = 1;
public Buzzer()
{
_kernel = new Library("kernel32");
_user = new Library("user32");
_window = new JWindow();
_window.setVisible(true);
} Please take a look at the new constant: it will be used in the
code below. The window will just provide its handle to hook the timer
to. Let's implement the startTimer
method: private void startTimer(long timeout)
{
Wnd hWnd = new Wnd(_window);
UInt eventID = new UInt(TIMER_ID);
UInt timeOutVal = new UInt(timeout);
UInt result = new UInt();
TimeOutCallback timeOutCallback = new TimeOutCallback();
Function setTimer = _user.getFunction("SetTimer");
setTimer.invoke(result, hWnd, eventID, timeOutVal, timeOutCallback); Passing a callback is even more straightforward than its
implementation: just create an object and pass it to the function that
requires it. It's just like an event listener. Some sanity check and
we're done: if (result.getValue() == 0)
{
throw new RuntimeException("Failed to create a timer, error code = " + getLastError(true));
}
} Before moving on, let's implement the
stopTimer method to be called from the
callback: private void stopTimer(Pointer.Void hwnd, UInt timerID)
{
Function killTimer = _user.getFunction("KillTimer");
killTimer.invoke(null, hwnd, timerID);
} Here we used JNIWrapper types as parameters, because we already
have them in the callback and there is no need to unwrap the values just
to wrap them back. 6.3. Testing ResultsLet's test the resulting code. Modify the main method in the
following way: public static void main(String[] args)
{
// Add jniwrap.dll directory to library loader search path
DefaultLibraryLoader.getInstance().addPath("bin");
Buzzer buzzer = new Buzzer();
if (!buzzer.checkOneInstance())
{
buzzer.error("Buzzer is already running");
System.exit(0);
}
buzzer.startTimer(5000);
} Compile the program. If it is already running, there is no control
to terminate it. To close the program, just press
Ctrl+C in its console (remember not to use
javaw.exe here or else you'll have to use the Task
Manager to stop the buzzing). Run the program. You should hear a regular
beeping at 5-second intervals until the program is terminated. This
means that the timer we are using is repetitive, so we'll need to stop
it when it is no longer needed. 6.4. Improving Callback CodeNow we'll complete the callback code with a few useful lines. We
may want to: Here's the whole implementation of the
callback method that does it all: public void callback()
{
stopTimer(_hwnd, _timerID);
buzz();
message("Timer has elapsed!");
System.exit(0);
} Note that although our main thread terminates before the timer
elapses, the program still lives because of non-daemon AWT threads that
keep our window alive. Take a look at the first line of the function. When the callback
is invoked, the variables specified at its creation are set to the
argument values before the callback method is
called. We pass two of the arguments to the
stopTimer function. Our callback does not have
a return value; if it had, we would need to assign the return value
parameter before leaving the callback method.
There are no limitations as to what things a callback can do other than
those specified for the original callback. Compile and run the application. Five seconds after the start, the
program will produce the usual sound, and then display the information
message box saying that the timer has elapsed. Closing this message box
will terminate the program. Chapter 7. Using Structures
In the previous chapter, we used an implementation-specific class to
get the window handle. In this chapter, we are going to get one in a more
legitimate way. We'll create a native window and later make it
custom-shaped like a splash screen. Creating and managing a window using Windows API requires passing
more complex parameters than we have used before, namely structures and
pointers. We'll begin with structures. Any window has its event queue and requires some thread to dispatch
its events. Messages are placed into the message queue in the form of the
MSG structures. Let's examine the contents of this structure and implement
it using JNIWrapper. Here is its definition: typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG; One of the MSG structure members is a structure itself:
POINT pt. Its layout is as follows: typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT; This one is really simple - it looks like a good starting point to
learn how to create structures in JNIWrapper. Structures can be
implemented by creating an instance of the
com.jniwrapper.Structure class and initilazing them
with objects representing fields. Most of them, however, are often reused
and so it is usually better (and more readable) to create a subclass for
each structure required. We'll choose the second approach: private static class POINT extends Structure
{
public LongInt _x = new LongInt();
public LongInt _y = new LongInt(); First, we created a variable for each structure member. These
variables are used for holding actual member data and for accessing it.
Next, the structure must be initialized by defining its layout. Members
are passed to the init method in the order they
appear in the native-side structure declaration. Optionally, the structure
alignment can be specified, but it is not needed here since all Windows
API structures have the default alignment (of 1). Here is the rest of the
code for this structure: public POINT()
{
init(new Parameter[] {_x, _y});
}
} The POINT structure is ready. Using the same principle,
let's create a class for the MSG structure: private static class MSG extends Structure
{
Pointer.Void _hwnd = new Pointer.Void();
UInt _message = new UInt();
UInt32 _wParam = new UInt32();
UInt32 _lParam = new UInt32();
UInt32 _time = new UInt32();
POINT _pt = new POINT();
public MSG()
{
init(new Parameter[]{_hwnd, _message, _wParam, _lParam, _time, _pt});
}
} Note that defining a member of some complex type (structure) is no
different from defining any member of simple ones. Uniformity is one of
the JNIWrapper's main design goals. Chapter 8. Using Pointers
To demonstrate the use of a pointer in JNIWrapper, let's implement
the event loop for our program. We'll do this before creating a window and
doing all other preparation stuff, because using a pointer here is the
simplest and most straightforward way. In fact, the only missing part will
be the window handle, so let's assume that it is already stored in the
_hSplash variable defined in the following way: private Pointer.Void _hSplash; Add the above line to the declaration section of the class
file. 8.1. Creating a Window Message LoopWe'll implement the message loop in the
run method of our program. The loop consists of
sequential invocation of three API functions:
GetMessage, TranslateMessage
and DispatchMessage. However, instead of taking the
MSG structure as their argument, they all require
LPMSG, i.e. a pointer to that structure. In JNIWrapper, pointers are similar to any other type. To create a
pointer, you need to create an instance of the
com.jniwrapper.Pointer class. Each pointer should
point to some other parameter called a referenced object. Whenever a
pointer is written or read, its referenced object is also written or
read, respectively. Let's create a pointer to the MSG structure: private void run()
{
Function getMessage = _user.getFunction("GetMessageA", null);
Function translateMessage = _user.getFunction("TranslateMessage", null);
Function dispatchMessage = _user.getFunction("DispatchMessageA", null);
MSG msg = new MSG();
Pointer msgPointer = new Pointer(msg); The code related to the pointer creation is marked bold. In this
example, msgPointer is a pointer and
msg is its referenced object. In the previous code we
used the Pointer.Void class instead of pointers,
because we did not care for the referenced object and did not need to
provide one. In this case, we need to provide the MSG structure so we
need to use the real pointer. The rest of this function code uses the
created pointer just as any other parameter: Bool result = new Bool();
for (;;)
{
getMessage.invoke(result, msgPointer, _hSplash, new UInt32(0),
new UInt32(0));
if (!result.getValue())
{
break;
}
translateMessage.invoke(null, msgPointer);
dispatchMessage.invoke(null, msgPointer);
}
} Although there is no need for us to access or modify data in the
msg structure, we could easily do so. 8.2. Pointers and StringsNow that we learned all the techniques, we can go into the gory
details of creating a window. First off, we will define the most complex
structure in this example: the equivalent of Windows API WNDCLASS
structure. We have already seen that any type can become a structure
member equally easily. This structure will be another example: it will
hold a callback reference. Add this class to your code: private static class WndClass extends Structure
{
private UInt32 _style = new UInt32();
private Callback _lpfnWndProc;
private Int32 _cbClsExtra = new Int32();
private Int32 _cbWndExtra = new Int32();
private Pointer.Void _hInstance = new Pointer.Void();
private Pointer.Void _hIcon = new Pointer.Void();
private Pointer.Void _hCursor = new Pointer.Void();
private Pointer.Void _hbrBackground = new Pointer.Void();
private AnsiString _lpszClassName = new AnsiString();
public WndClass(Callback windowProc, String className,
Pointer.Void bgBrushHandle)
{
_style.setValue(3); // CS_HREDRAW | CS_VREDRAW
_lpfnWndProc = windowProc;
_lpszClassName.setValue(className);
_cbClsExtra.setValue(0);
_cbWndExtra.setValue(0);
_hInstance.setValue(0);
_hIcon.setValue(0);
_hCursor.setValue(0);
_hbrBackground.setValue(bgBrushHandle.getValue());
init(new Parameter[] {_style, _lpfnWndProc, _cbClsExtra,
_cbWndExtra, _hInstance, _hIcon, _hCursor,
_hbrBackground, new Pointer.Void(0),
new Pointer(_lpszClassName)});
}
} 8.3. Using String Values in StructuresTake a look at the initialization of the
_lpszClassName field: init(new Parameter[] {... new Pointer(_lpszClassName)}); When defining structures that contain strings, you should always
remember that there can be two types of them: pointers to character data
and character arrays. To distinguish between the two, look at the member
definition. Ones defined as char *name are pointers
and others defined as char name[20] are arrays. In
function calls, strings are always passed as pointers, so string
parameters are automatically converted. But in structures, there is no
way to know, so you should explicitly specify how the string is stored.
If it is a pointer, a Pointer instance must be
passed as the corresponding structure member. If it is an array, you
should create a string with the maximum length equal to that of the
expected character array and pass that parameter itself as the structure
member. In this case, we have a pointer to characters version. Chapter 9. Final Touch
Now we are ready to finish the application. To do this, we need to
implement a window procedure callback, register a window class, create a
window, shape and center it, and define a window painting method. There is
nothing special in any of these tasks so you can just take a look at the
final code provided with this tutorial. After running it, you should see a
small round window (on top of all other windows), in ten seconds the
computer should make a beeping sound and pop up a message box saying that
the timer has elapsed. Of course, the single instance rule is still
enforced. The tutorial is over. By now you learned everything you need to
start successfully using JNIWrapper in your applications. |
| Copyright © 2002-2021 TeamDev Ltd. | |
|
|